package ganga.custodian.sensors;

import android.app.Service;
import android.content.Intent;
import android.graphics.RectF;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.GpsSatellite;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.location.GpsStatus;
import android.os.Bundle;
import android.os.IBinder;
import android.widget.Toast;

import ganga.custodian.CustodianApplication;
import ganga.custodian.human.HumanModel;
import ganga.custodian.human.ModelMonitor;

/**
 * Получает от датчиков значения, передаёт МоделиЧеловека
 * @author Apocrypha
 * @version 0.2
 * @updated 15-июл-2011 21:06:30
 */
public class SensorsMonitor extends Service implements GpsStatus.Listener, LocationListener, SensorEventListener
{

	private static final Integer defaultInterval = 900;
	private long lastHeartBeatTimeStamp;
	private boolean isFirstHeartBeatEvent;
	private CustodianApplication application;
    // The location manager, from which we get location updates.
	private LocationManager locationManager;
    // The sensor manager, which we use to interface to all sensors.
    private SensorManager sensorManager;
	// Display panes for the network and GPS locations, with heading,
	// position, and course data for each.
	//TODO: private GeoElement netElement;
    //TODO: private GeoElement gpsElement;


	// Location providers we monitor.
    private static final String[] locationProviders = {
    	LocationManager.NETWORK_PROVIDER,
		LocationManager.GPS_PROVIDER
	};
	// Latest GPS status.  If null, we haven't got one yet.
	private GpsStatus gpsStatus = null;
	// Cached satellite info.  Indexed by the satellite's PRN number,
	// which is in the range 1-NUM_SATS.
	private GpsInfo[] satCache;
	// Number of satellites for which we have info.
	private int numSats;
    // The most recent network and GPS locations.
    private Location netLocation = null;
    private Location gpsLocation = null;


    /**
     * Number of GPS satellites we can handle.  Satellites are numbered 1-32;
     * this is the "PRN number", i.e. a number which identifies the
     * satellite's PRN, which is a 1023-bit number.
     */
    static final int NUM_SATS = 32;
    /**
     * Cached info on a satellite's status.
     */
    static final class GpsInfo {
        GpsInfo(int prn) {
            this.prn = prn;
            this.name = "" + prn;
        }
        
        final int prn;
        final String name;
        
        // Time at which this status was retrieved.  If 0, not valid.
        long time = 0;
        
        float azimuth;
        float elev;
        float snr;
        boolean hasAl;
        boolean hasEph;
        boolean used;
        
        // Time at which this satellite was last used in a fix.
        long usedTime = 0;
        
        // Bounding rectangle for the display in the sky view.
        RectF rect;
        
        // Label position for the display in the sky view.
        float textX;
        float textY;
        
        // Colour to plot it with, based on status.
        int colour;
    }
    // Time in ms for which cached satellite data is valid.
    private static final int DATA_CACHE_TIME = 10 * 1000;

    // Colors to represent sat status by.
    private static final int[] COLOUR_PLOT = {
        0xff00ffff, 0xff00ff00, 0xffffff00, 0xffff9000,
    };
	private HumanModel humanModel;
	public ModelMonitor m_ModelMonitor;
	// Display pane for satellite status.  It's current bounds.
    //TODO: private SatelliteElement satElement;

	public SensorsMonitor(){
		application = CustodianApplication.getInstance();
		locationManager = application.locationManager;
		sensorManager = application.sensorManager;
	}
	/**
	 * Start this view.  This notifies the view that it should start
	 * receiving and displaying data.  The view will also get tick events
	 * starting here.
	 */
	void startGPSSensor() {
        // Register for location updates.
        for (String name : locationProviders) {
        	LocationProvider prov = locationManager.getProvider(name);
        	if (prov != null) {
        		locationManager.requestLocationUpdates(name, 60000, 0f, this);
        		
        		// For the GPS, also add a GPS status listener to get
        		// additional satellite and fix info.
        		if (name.equals(LocationManager.GPS_PROVIDER))
        		    locationManager.addGpsStatusListener(this);
        		
        		// Prime the pump with the last known location.
        		Location prime = locationManager.getLastKnownLocation(name);
        		if (prime != null)
        			onLocationChanged(prime);
        	}
        }
        
        // Get orientation updates.
        registerSensor(Sensor.TYPE_ACCELEROMETER);
        registerSensor(Sensor.TYPE_MAGNETIC_FIELD);
	}
	private final void registerSensor(int type) {
        Sensor sensor = sensorManager.getDefaultSensor(type);
        if (sensor != null)
            sensorManager.registerListener(this, sensor,
                                           SensorManager.SENSOR_DELAY_GAME);
	}

	
	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		lastHeartBeatTimeStamp = 0;
		isFirstHeartBeatEvent = true;
		locationMonitorInitialize();
		startGPSSensor();
	}
	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
	}
	/** 
	 * Services are launched on the main Application (or process) thread, 
	 * meaning that any processing done in the
	 * onStartCommand handler will happen on the main GUI thread.
	 * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		super.onStartCommand(intent, flags, startId);
		//Получено оповещение о новом ударе сердца
	
		//Рабор сообщения
		Integer duration = getInterval(intent);
		
		Intent intentForModelMonitor = new Intent("ganga.custodian.human.ModelMonitor");
		intentForModelMonitor.putExtra("RRInervalDuration", duration);
		startService(intentForModelMonitor);
	
		
				
		
		return Service.START_STICKY;
	}
	private void showLocationByToast() {
		String toastMessage;
		Location location = getLocation();
		if (location == null)
			toastMessage = "null";
		else {
			location.getLatitude();
			toastMessage = String.format("Lat: %f", location.getLatitude());
		}
		Toast
			.makeText(getApplicationContext(), toastMessage, Toast.LENGTH_SHORT)
			.show();
	}
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	
	/**
	 * Calulate interval duration
	 * @param intent
	 * @return
	 */
	private Integer getInterval(Intent intent) {
		Long timeStamp = intent.getLongExtra("TimeStamp", 0L);

		if (isFirstHeartBeatEvent){
			lastHeartBeatTimeStamp = timeStamp;
			isFirstHeartBeatEvent = false;
			return defaultInterval;
		}

		int RRInterval = (int) (timeStamp - lastHeartBeatTimeStamp);
		lastHeartBeatTimeStamp = timeStamp;
		return RRInterval;
	}
	Location getLocation(){
		boolean isOn = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
		Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		return netLocation;
	}
    /**
     * Called to report changes in the GPS status.
     * 
     * @param   event           Event number describing what has changed.
     */
	@Override
	public void onGpsStatusChanged(int event) {
	    try {
	        switch (event) {
	        case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
	            gpsStatus = locationManager.getGpsStatus(gpsStatus);
	            Iterable<GpsSatellite> sats = gpsStatus.getSatellites();
	            long time = System.currentTimeMillis();
	            for (GpsSatellite sat : sats) {
	                int prn = sat.getPrn();
	                if (prn < 1 || prn > NUM_SATS)
	                    continue;

	                GpsInfo ginfo = satCache[prn];
	                ginfo.time = time;
	                ginfo.azimuth = sat.getAzimuth();
	                ginfo.elev = sat.getElevation();
	                ginfo.snr = sat.getSnr();
	                ginfo.hasAl = sat.hasAlmanac();
	                ginfo.hasEph = sat.hasEphemeris();
	                ginfo.used = sat.usedInFix();
	            }

	            //            // Fake some satellites, for testing.
	            //            Random r = new Random();
	            //            r.setSeed(4232);
	            //            for (int i = 1; i <= NUM_SATS; ++i) {
	            //                GpsInfo ginfo = satCache[i];
	            //                if (i % 3 == 0) {
	            //                    ginfo.time = time - r.nextInt(5000);
	            //                    ginfo.azimuth = r.nextFloat() * 360.0f;
	            //                    ginfo.elev = r.nextFloat() * 90.0f;
	            //                    ginfo.snr = 12;
	            //                    ginfo.hasAl = r.nextInt(4) != 0;
	            //                    ginfo.hasEph = ginfo.hasAl && r.nextInt(3) != 0;
	            //                    ginfo.used = ginfo.hasEph && r.nextBoolean();
	            //                } else {
	            //                    ginfo.time = 0;
	            //                }
	            //            }

	            // Post-process the sats.
	            numSats = 0;
	            for (int prn = 1; prn <= NUM_SATS; ++prn) {
	                GpsInfo ginfo = satCache[prn];
	                if (time - ginfo.time > DATA_CACHE_TIME) {
	                    ginfo.time = 0;
	                    ginfo.usedTime = 0;
	                } else {
	                    if (ginfo.used)
	                        ginfo.usedTime = time;
	                    else if (time - ginfo.usedTime <= DATA_CACHE_TIME)
	                        ginfo.used = true;
	                    else
	                        ginfo.usedTime = 0;
	                    int colour = ginfo.used ? 0 : ginfo.hasEph ? 1 : ginfo.hasAl ? 2 : 3;
	                    ginfo.colour = COLOUR_PLOT[colour];
	                    ++numSats;
	                }
	            }

	            //TODO:satElement.setValues(satCache, numSats);
	            break;
	        case GpsStatus.GPS_EVENT_STARTED:
	        case GpsStatus.GPS_EVENT_STOPPED:
	        case GpsStatus.GPS_EVENT_FIRST_FIX:
	            break;
	        }
	    } catch (Exception e) {
	        e.printStackTrace();
	    }
	}
	@Override
	public void onLocationChanged(Location location) {
		// TODO Auto-generated method stub
	    try {
	        synchronized (this) {
	            if (location.getProvider().equals(LocationManager.NETWORK_PROVIDER)) {
	                netLocation = location;
	              //TODO: netElement.setValue(location);
	            } else if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
	                gpsLocation = location;
	              //TODO: gpsElement.setValue(location);
	            }
	        }
	    } catch (Exception e) {
	        e.printStackTrace();
	    }
	    
	}
	@Override
	public void onProviderDisabled(String provider) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void onProviderEnabled(String provider) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void onSensorChanged(SensorEvent event) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * TODO: Перенести в конструктор
	 */
	public void locationMonitorInitialize() {
		// Set up the satellite data cache. For simplicity, we allocate
		// NUM_SATS + 1 so we can index by PRN number.
		satCache = new GpsInfo[NUM_SATS + 1];
		for (int i = 1; i <= NUM_SATS; ++i)
			satCache[i] = new GpsInfo(i);
		numSats = 0;
	}
}